#include "Python.h"
//#include "structmember.h"

#ifdef MS_WINDOWS
  #define WIN32_LEAN_AND_MEAN
//  #define FD_SETSIZE 128
  #include <winsock2.h>
  typedef SOCKET Py_sockfd_t
  #define INVALID_SOCKFD INVALID_SOCKET
  #define BLOCKING_ERROR (WSAGetLastError() == WSAEWOULDBLOCK)
#else
  #include <sys/socket.h>
  typedef int Py_sockfd_t
  #define INVALID_SOCKFD -1
  #define BLOCKING_ERROR (errno == EWOULDBLOCK)
#endif

typedef struct
{
	PyObject_HEAD
	Py_sockfd_t fd;
	unsigned int readable   : 1;
	unsigned int writable   : 1;
	unsigned int closefd    : 1;
	unsigned int listening  : 1;
	unsigned int connecting : 1;
    PyObject *weakref;
    PyObject *dict;
} socketio;

PyTypeObject PySocketIO_Type;

#define PySocketIO_Check(obj) (PyObject_TypeCheck((obj), &PySocketIO_Type))

#define _socketio_isclosed(obj) ((obj)->fd == INVALID_SOCKFD)
#define _socketio_isreadable(obj) ((obj)->readable && !(obj)->listening && !(obj)->connecting)
#define _socketio_iswritable(obj) ((obj)->writable && !(obj)->listening && !(obj)->connecting)

static PyObject *
_socketio_closerror()
{
    return PyErr_Format(PyExc_ValueError, "I/O operation on closed socket");
}

static PyObject *
_socketio_moderror(char *action)
{
    return PyErr_Format(PyExc_ValueError, "Socket not open for %s", action);
}

static PyObject *
_socketio_ioerror()
{
#ifdef MS_WINDOWS
	return PyErr_SetExcFromWindowsErr(PyExc_IOError, 0);
#else
	return PyErr_SetFromErrno(PyExc_IOError);
#endif
}

static PyObject *
socket_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    socketio *self;

    assert(type != NULL && type->tp_alloc != NULL);

    self = (socketio *) type->tp_alloc(type, 0);
    if (self)
	{
        self->fd = INVALID_SOCKFD;
        self->readable = 0;
        self->writable = 0;
        self->closefd = 1;
		self->listening = 0;
		self->connecting = 0;
        self->weakref = NULL;
    }

    return (PyObject *) self;
}

static int
socket_init(PySocketObject *self, PyObject *args, PyObject *kwds)
{
//	PySocketObject *self = (PySocketObject*) oself;
	static char *kwlist[] = {"socket", "mode", "closefd", "timeout", 
							 "backlog", "openargs", NULL};
	PyObject *nameobj;

	assert(PySocketIO_Check(self));
	if (!_socketio_isclosed(self))
		pass;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|si:socketio",
									 kwlist, &nameobj, &mode, &closefd))
		return -1;

    if (PyFloat_Check(nameobj))
	{
        PyErr_SetString(PyExc_TypeError, "integer file descriptor expected");
        return -1;
    }

    fd = PyLong_AsLong(nameobj);
    if (fd < 0)
	{
        if (!PyErr_Occurred())
		{
            PyErr_SetString(PyExc_ValueError, "negative file descriptor");
            return -1;
        }
        PyErr_Clear();
    }
	Py_BEGIN_ALLOW_THREADS
	fd = socket(family, socktype, proto);
	Py_END_ALLOW_THREADS

	if (fd == INVALID_SOCKFD)
		pass;
	self->fd = fd;
	if (timeout >= 0.0)
		sock_settimeout(timeout);
	if (passive)
	{
		bind(fd, &sockaddr, sizeof(sockaddr));
		listen(fd, backlog);
	}
	else
	{
		if (connect(fd, &sockaddr, sizeof(sockaddr)))
		{
			err = sockerr;
#ifdef MS_WINDOWS
			if (err == WSAEWOULDBLOCK)
#else
			if (err == EINPROGRESS || err == EINTR)
#endif
			
	}


}

static int
socketio_traverse(socketio *self, visitproc visit, void *arg)
{
	assert(PySocketIO_Check(self));
    Py_VISIT(self->dict);
    return 0;
}

static int
socketio_clear(socketio *self)
{
	assert(PySocketIO_Check(self));
    Py_CLEAR(self->dict);
    return 0;
}

static void
socketio_dealloc(socketio *self)
{
	assert(PySocketIO_Check(self));
    if (_PyIOBase_finalize((PyObject *) self))
        return;
    _PyObject_GC_UNTRACK(self);
    if (self->weakref)
        PyObject_ClearWeakRefs((PyObject *) self);
    Py_CLEAR(self->dict);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
socket_repr(socketio *self)
{
    PyObject *nameobj, *res;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return PyString_FromFormat("<%s [closed]>", 
									Py_TYPE(self)->tp_name);

    nameobj = PyObject_GetAttrString((PyObject *) self, "name");
    if (!nameobj)
	{
        if (PyErr_ExceptionMatches(PyExc_AttributeError))
            PyErr_Clear();
        else
            return NULL;
        res = PyString_FromFormat("<%s fd=%d mode='%s'>",
								   Py_TYPE(self)->tp_name,
                                   self->fd, mode_string(self));
    }
    else
	{
        PyObject *repr = PyObject_Repr(nameobj);
        Py_DECREF(nameobj);
        if (!repr)
            return NULL;
        res = PyString_FromFormat("<%s name=%s mode='%s'>",
								   Py_TYPE(self)->tp_name,
                                   PyString_AS_STRING(repr),
                                   mode_string(self));
        Py_DECREF(repr);
    }
    return res;
}

static PyObject *
_socketio_recv_size(Py_sockfd_t sockfd, Py_ssize_t size, int flags)
{
    PyObject *bytes;
    Py_ssize_t count;

    bytes = PyBytes_FromStringAndSize(NULL, size);
    if (!bytes)
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    count = recv(sockfd, PyBytes_AS_STRING(bytes), size, flags);
    Py_END_ALLOW_THREADS

    if (count < 0)
	{
        Py_DECREF(bytes);
        if (BLOCKING_ERROR)
            Py_RETURN_NONE;
		else
			return _socketio_error();
    }

    if (count < size)
		_PyBytes_Resize(&bytes, count)

	return bytes;
}

static PyObject *
socketio_recv(socketio *self, PyObject *args)
{
    Py_ssize_t size = -1;
	int flags = 0;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_isreadable(self))
		return _socketio_moderror("reading");

    if (!PyArg_ParseTuple(args, "O&|i:recv", &_PyIO_ConvertSsize_t, &size, &flags))
        return NULL;
	if (size < 0)
        return PyErr_Format(PyExc_ValueError, "negative buffersize in recv");

	return _socketio_recv_size(self->fd, size, flags);
}

static PyObject *
socketio_read(socketio *self, PyObject *args)
{
    Py_ssize_t size = -1;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_isreadable(self))
		return _socketio_moderror("reading");

    if (!PyArg_ParseTuple(args, "|O&:read", &_PyIO_ConvertSsize_t, &size))
        return NULL;
    if (size < 0)
        return socketio_readall(self);

	return _socketio_recv_size(self->fd, size, 0);
}

static PyObject *
_socketio_recv_buffer(Py_sockfd_t sockfd, Py_buffer *buffer, int flags)
{
    Py_ssize_t count;

	Py_BEGIN_ALLOW_THREADS
    count = recv(sockfd, buffer->buf, buffer->len, flags);
    Py_END_ALLOW_THREADS

	PyBuffer_Release(buffer);
    if (count < 0)
	{
        if (BLOCKING_ERROR)
            Py_RETURN_NONE;
		else
			return _socketio_error();
    }

    return PyInt_FromSsize_t(count);
}

static PyObject *
socketio_recvinto(socketio *self, PyObject *args)
{
    Py_buffer buffer;
	int flags = 0;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_isreadable(self))
		return _socketio_moderror("reading");

    if (!PyArg_ParseTuple(args, "w*|i:recvinto", &buffer, &flags))
        return NULL;

	return _socketio_recv_buffer(self->fd, &buffer, flags);
}

static PyObject *
socketio_readinto(socketio *self, PyObject *args)
{
    Py_buffer buffer;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_isreadable(self))
		return _socketio_moderror("reading");

    if (!PyArg_ParseTuple(args, "w*:readinto", &buffer))
        return NULL;

	return _socketio_recv_buffer(self->fd, &buffer, 0);
}

static PyObject *
_socketio_send_buffer(Py_sockfd_t sockfd, Py_buffer *buffer, int flags)
{
    Py_ssize_t count;

    Py_BEGIN_ALLOW_THREADS
    count = send(sockfd, buffer->buf, buffer->len, flags);
    Py_END_ALLOW_THREADS

    PyBuffer_Release(buffer);
    if (count < 0)
	{
        if (BLOCKING_ERROR)
            Py_RETURN_NONE;
		else
			return _socketio_ioerror();
    }

    return PyInt_FromSsize_t(count);
}

static PyObject *
socket_send(socketio *self, PyObject *args)
{
    Py_buffer buffer;
	int flags = 0;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_iswritable(self))
		return _socketio_moderror("writing");

    if (!PyArg_ParseTuple(args, "s*|i:send", &buffer, &flags))
        return NULL;

	return _socketio_send_buffer(self->fd, &buffer, flags);
}

static PyObject *
socket_write(socketio *self, PyObject *args)
{
    Py_buffer buffer;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
	if (!_socketio_iswritable(self))
		return _socketio_moderror("writing");

    if (!PyArg_ParseTuple(args, "s*:write", &buffer))
        return NULL;

	return _socketio_send_buffer(self->fd, &buffer, 0);
}

static PyObject *
socketio_getopt(socketio *self, PyObject *args)
{
	int res, level, option, intval;
	PyObject *objval = NULL;
	Py_ssize_t size = 0;
	char *bufptr;
	int buflen;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();

    if (!PyArg_ParseTuple(args, "ii|n:getopt", &level, &option, &size))
        return NULL;

	if (size == 0)
	{
		intval = 0;
		bufptr = &intval;
		buflen = sizeof(intval);
	}
	else
	{
		objval = PyBytes_FromStringAndSize(NULL, size);
		if (!objval)
			return NULL;
		bufptr = PyBytes_AS_STRING(objval);
		buflen = size;
	}

	Py_BEGIN_ALLOW_THREADS
	res = getsockopt(self->fd, level, option, bufptr, &buflen)
	Py_END_ALLOW_THREADS

	if (res)
	{
		Py_XDECREF(objval);
		return _socketio_ioerror();
	}

	if (size == 0)
		objval = PyInt_FromLong(intval);
	else
		_PyBytes_Resize(&objval, buflen);
	return objval;
}

PyDoc_STRVAR(getopt_doc,
"getopt(level, option[, buffersize]) -> value\n\
\n\
Get a socket option.  See the OS manual for level and option.\n\
If a nonzero buffersize argument is given, the return value is a\n\
byte string of that length; otherwise it is an integer.");

static PyObject *
socketio_setopt(socketio *self, PyObject *args)
{
	int res, level, option, intval;
	PyObject *objval;
	char *bufptr;
	int buflen;

	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();

    if (!PyArg_ParseTuple(args, "iiO:setopt", &level, &option, &objval))
        return NULL;

	if (PyInt_Check(objval))
	{
		intval = PyInt_AsLong(objval);
		bufptr = &intval;
		buflen = sizeof(intval);
	}
	else if (PyBytes_Check(objval))
	{
		bufptr = PyBytes_AsBytes(objval);
		buflen = PyBytes_Size(objval);
	}
	else
		return PyErr_Format(PyExc_TypeError, "option value must be integer or bytes");

	Py_BEGIN_ALLOW_THREADS
	res = setsockopt(self->fd, level, option, bufptr, buflen)
	Py_END_ALLOW_THREADS

	if (res)
		return _socketio_ioerror();
	Py_RETURN_NONE;
}

PyDoc_STRVAR(setopt_doc,
"setopt(level, option, value)\n\
\n\
Set a socket option.  See the OS manual for level and option.\n\
The value argument can either be an integer or byte string.");

static PyObject *
socketio_readable(socketio *self)
{
	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
    return PyBool_FromLong(_socketio_isreadable(self));
}

static PyObject *
socketio_writable(socketio *self)
{
	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
        return _socketio_closerror();
    return PyBool_FromLong(_socketio_iswritable(self));
}

static PyObject *
socketio_fileno(socketio *self)
{
	assert(PySocketIO_Check(self));
    if (_socketio_isclosed(self))
		return _socketio_closerror()
    return PyInt_FromLong(self->fd);
}

PyDoc_STRVAR(fileno_doc,
"fileno() -> int. \"file descriptor\".\n"
"\n"
"This is needed for lower-level file interfaces, such the fcntl module.");

static PyMethodDef socketio_methods[] =
{
	// socket-specific methods
    {"getopt",  (PyCFunction) socketio_getopt,    METH_VARARGS, getopt_doc},
    {"setopt",  (PyCFunction) socketio_setopt,    METH_VARARGS, setopt_doc},
    //{"getsockname", (PyCFunction) socketio_getsockname, METH_NOARGS,  getsockname_doc},
    //{"getpeername", (PyCFunction) socketio_getpeername, METH_NOARGS,  getpeername_doc},
    {"atmark",   (PyCFunction) socketio_atmark,   METH_NOARGS,  atmark_doc},
    {"accept",   (PyCFunction) socketio_accept,   METH_NOARGS,  accept_doc},
    {"shutdown", (PyCFunction) socketio_shutdown, METH_O,       shutdown_doc},
    {"recv",     (PyCFunction) socketio_recv,     METH_VARARGS, recv_doc},
    {"recvinto", (PyCFunction) socketio_recvinto, METH_VARARGS, recv_doc},
    {"send",     (PyCFunction) socketio_send,     METH_VARARGS, send_doc},
	// rawio generic methods
    {"read",     (PyCFunction) socketio_read,     METH_VARARGS, read_doc},
    {"readall",  (PyCFunction) socketio_readall,  METH_NOARGS,  readall_doc},
    {"readinto", (PyCFunction) socketio_readinto, METH_VARARGS, readinto_doc},
    {"write",    (PyCFunction) socketio_write,    METH_VARARGS, write_doc},
    {"close",    (PyCFunction) socketio_close,    METH_NOARGS,  close_doc},
    {"readable", (PyCFunction) socketio_readable, METH_NOARGS,  readable_doc},
    {"writable", (PyCFunction) socketio_writable, METH_NOARGS,  writable_doc},
    {"fileno",   (PyCFunction) socketio_fileno,   METH_NOARGS,  fileno_doc},
    {NULL}
};

static PyObject *
socketio_getclosed(socketio *self, void *closure)
{
	assert(PySocket_Check(self));
    return PyBool_FromLong((long) PySocket_Closed(self));
}

PyDoc_STRVAR(closed_doc, "True if the socket is closed");

static PyObject *
socketio_getmode(socketio *self, void *closure)
{
    return PyUnicode_FromString(mode_string(self));
}

PyDoc_STRVAR(mode_doc, "String giving the socket mode");

static PyGetSetDef socketio_getsets[] =
{
    {"closed",  (getter) socketio_getclosed,  NULL, closed_doc},
    {"mode",    (getter) socketio_getmode,    NULL, mode_doc},
    {"timeout", (getter) socketio_gettimeout, NULL, timeout_doc},
    {NULL}
};

PyTypeObject PySocketIO_Type =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "_netio.SocketIO",                          /* tp_name */
    sizeof(socketio),                           /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor) socketio_dealloc,              /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc) socketio_repr,                   /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
                       | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    socketio_doc,                               /* tp_doc */
    (traverseproc) socketio_traverse,           /* tp_traverse */
    (inquiry) socketio_clear,                   /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(socketio, weakref),                /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    socketio_methods,                           /* tp_methods */
    0,                                          /* tp_members */
    socketio_getsets,                           /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    offsetof(socketio, dict),                   /* tp_dictoffset */
    socketio_init,                              /* tp_init */
    PyType_GenericAlloc,                        /* tp_alloc */
    socketio_new,                               /* tp_new */
    PyObject_GC_Del,                            /* tp_free */
};

#ifdef MS_WINDOWS

static void
os_cleanup(void)
{
    WSACleanup();
}

static int
os_init(PyObject *mod)
{
	WSADATA wsaData = { 0 };
	int ret;

	ret = WSAStartup(WINSOCK_VERSION, &wsaData);
	if (ret)
	{
		PyErr_SetFromWindowsErr(ret);
		return -1;
	}

	PyModule_AddIntConstant(mod, "_WINSOCK_VERSION", wsaData.wVersion);
	PyModule_AddStringConstant(mod, "_WINSOCK_DESCRIPTION", wsaData.szDescription);
	Py_AtExit(os_cleanup);
	return 0;
}

#else

static int
os_init(PyObject *)
{
}

#endif /* MS_WINDOWS */

PyMODINIT_FUNC
init_netio()
{
	PyObject *mod;

	mod = Py_InitModule3("_netio", netio_methods, netio_doc);
	if (!mod)
		return;

	if (os_init(mod))
		goto fail;

	PySocketIO_Type.tp_base = &PyRawIOBase_Type;
	if (PyType_Ready(&PySocketIO_Type))
        goto fail;
	Py_INCREF(&PySocketIO_Type);
    if (PyModule_AddObject(mod, "SocketIO", (PyObject *) &PySocketIO_Type))
		goto fail;

	PyBufferedSequential_Type.tp_base = &PyBufferedIOBase_Type;
	if (PyType_Ready(&PyBufferedSequential_Type))
        goto fail;
	Py_INCREF(&PyBufferedSequential_Type);
    if (PyModule_AddObject(mod, "BufferedSequential", (PyObject *) &PyBufferedSequential_Type))
		goto fail;

	if (PyType_Ready(&PyIOPoller_Type))
        goto fail;
	Py_INCREF(&PyIOPoller_Type);
    if (PyModule_AddObject(mod, "IOPoller", (PyObject *) &PyIOPoller_Type))
		goto fail;

	if (PyType_Ready(&PyAsyncIOPoller_Type))
        goto fail;
	Py_INCREF(&PyAsyncIOPoller_Type);
    if (PyModule_AddObject(mod, "AsyncIOPoller", (PyObject *) &PyAsyncIOPoller_Type))
		goto fail;

	return;

fail:
	Py_DECREF(mod);
}

static PyObject *
ex_foo(PyObject *self, PyObject *args)
{
	printf("Hello, world\n");
	Py_INCREF(Py_None);
	return Py_None;
}

static PyMethodDef example_methods[] = {
	{"foo", ex_foo, METH_VARARGS, "foo() doc string"},
	{NULL, NULL}
};

PyMODINIT_FUNC
initexample(void)
{
	Py_InitModule("example", example_methods);
}
